1. 概述

Redis (/ˈrɛdɪs/; Remote Dictionary Server) is an in-memory data structure store, used as a distributed, in-memory key–value database, cache and message broker, with optional durability. Redis supports different kinds of abstract data structures, such as strings, lists, maps, sets, sorted sets, HyperLogLogs, bitmaps, streams, and spatial indexes. The project is developed and maintained by a project core team and as of 2015 is sponsored by Redis Labs. It is open-source software released under a BSD 3-clause license.

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

Redis会周期性地把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并在此基础上实现了主从(master-salve)同步,其是当下最热门的NoSQL技术之一,称之为结构化数据库,其性能能够达到读速度110000次/s,写速度81000次/s。

2. 数据类型

Redis的外围由一个键、值映射的字典构成,与其他非关系型数据库主要不同在于:Redis中值的类型不止于字符串,还支持多种抽象数据类型,其提供了**五种数据类型:string, hash, list, set 以及 zset(sorted set)**。

2.1 String

String类型中Value值最大可容纳的数据长度为512MB

字符串(String)是Redis中最常用的类型,是一个由字节组成的序列,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据。

1
2
3
4
5
6
7
8
9
# 设置string类型的缓存数据,key为hello,value为world
127.0.0.1:6379> set hello world
OK
# 获取对应的value
127.0.0.1:6379> get hello
"world"
# 获取hello数据类型
127.0.0.1:6379> type hello
string

2.2 List

List类型中可以包含的最大元素数量为4294967295

列表(List)表示允许从序列的两端推入或弹出元素,列表由多个字符串值组成的可重复的序列,是链表结构,所以从两端进行插入或弹出的时间复杂度为O(1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 从左推入元素
127.0.0.1:6379> LPUSH iamlistkey 1 2 3 4
(integer) 4
# 显示范围内元素(start:0 ~ end:-1)其中end为-1表示查看后续的所有元素
127.0.0.1:6379> LRANGE iamlistkey 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
# 从右推入元素
127.0.0.1:6379> RPUSH iamlistkey 5 6 7
(integer) 7
# 显示范围内元素(start:0 ~ end:-1)其中end为-1表示查看后续的所有元素
127.0.0.1:6379> LRANGE iamlistkey 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "5"
6) "6"
7) "7"
  • 应用场景
    • 热搜排行榜
    • 消息队列

2.3 Hash

一个Hash散列中可以存储4294967295个键值对

相当于一个key中存在多个map,Redis中的散列可以看成具有String key和String value的map容器,可以将多个key-value存储到一个key中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 设置Key为iammapkey,其中value为一个键值对,键为name,值为test
127.0.0.1:6379> hset iammapkey name test
(integer) 0
# 添加一个键值对,键为age,值为22
127.0.0.1:6379> hset iammapkey age 22
(integer) 0
# 获取键为name的值
127.0.0.1:6379> hget iammapkey name
"test"
# 获取键为age的值
127.0.0.1:6379> hget iammapkey age
"22"
# 获取散列中键为`iammapkey`的所有键值
127.0.0.1:6379> hkeys iammapkey
1) "name"
2) "age"

2.4 Set

一个Set集合可以容纳的最大元素数量为4294967295

集合(Set)是无序不重复的,此处的无需是数据不重复。和列表一样,在执行插入的时候判断是否存在某元素,集合最大的优势在于其可以进行交集、并集和差集的操作。

1
2
3
4
5
6
7
8
9
10
# 设置Key为iamsetkey,其中value为三个分别是"zchengb","zxchengb","zchengb"
127.0.0.1:6379> SADD iamsetkey zchengb zxchengb zchengb
(integer) 2
# 使用SMEMBER显示该Set集合中的所有值(去重)
127.0.0.1:6379> SMEMBERS iamsetkey
1) "zxchengb"
2) "zchengb"
# 查询Set集合中对应Key的Value数量
127.0.0.1:6379> SCARD iamsetkey
(integer) 2

2.5 ZSet

ZSet为有序集合(Sorted Set),有顺序,不能重复,此处的不能重复是指ZSet中的键值对中的值不可重复,可按照升序或降序输出集合序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 设置Key为iamzsetkey,其中value为四个键值对
127.0.0.1:6379> ZADD iamzsetkey 1 v1 2 v2 3.0 v3 3.2 v4
(integer) 4
# 按照升序打印集合中的所有的值,其顺序按照Score(键值对中的键)来排序,而输出的为Member
127.0.0.1:6379> ZRANGE iamzsetkey 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
# 按照降序打印集合中的所有值
127.0.0.1:6379> ZREVRANGE iamzsetkey 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
# 区间统计,统计Score为0~3的个数
127.0.0.1:6379> ZCOUNT iamzsetkey 0 3
(integer) 3

3. 安装

3.1 Docker

Tips:本节所述需安装Docker使用

关于Linux下Docker的安装及使用可参考该文章

  • 拉取Redis镜像
1
$ docker pull redis
  • 新建挂载配置文件夹

Docker中的Redis 默认配置只能够本地连接(因为其默认绑定允许连接的IP是127.0.0.1),所以在使用 Redis Desktop Manager连接会报错,因此需要手动挂载Redis 配置文件

1
2
$ mkdir -p /root/docker/redis/data
$ mkdir -p /root/docker/redis/conf
  • 增加配置文件,内容如下
1
2
3
4
5
# bind 127.0.0.0
# 保护模式:开启(即操作需经过认证)
protected-mode yes
# 认证密码
requirepass xxx
  • 创建Redis容器并启动
1
$ docker run --name redis -p 6379:6379 -v /root/docker/redis/data:/data -v /root/docker/redis/conf/redis.conf:/etc/redis/redis.conf -d redis redis-server /etc/redis/redis.conf

至此,Docker启动Redis完成。

3.2 Windows

实际上Redis官方并没有提供Windows版的Redis,目前互联网中存在的Windows版Reids均为第三方实现的

可前往微软开源项目中下载Windows版Redis,其中选择Redis-xxx.msi进行安装,安装完成后,在Redis目录下双击打开redis-server.exe即可成功运行Redis。

目录下文件如下

  • redis-server.exe:服务端程序,提供 redis 服务
  • redis-cli.exe: 客户端程序,通过它连接 redis 服务并进行操作
  • redis-check-dump.exe:RDB 文件修复工具
  • redis-check-aof.exe:AOF 文件修复工具
  • redis-benchmark.exe:性能测试工具,用以模拟同时由 N 个客户端发送 M 个 SETs/GETs 查询 (类似于 Apache 的 ab 工具)
  • redis.windows.conf: 配置文件,将 redis 作为普通软件使用的配置,命令行关闭则 redis 关闭
  • redis.windows-service.conf:配置文件,将 redis 作为系统服务的配置

4. 整合Spring Boot

4.1 Maven依赖

Spring Boot整合Redis依赖情况如下所示,在使用父级pom为spring-boot-starter-parent时仅需导入spring-boot-starter-data-redis依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/>
</parent>

<dependencies>
...
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
...
</dependencies>

4.2 配置YAML

application.yml文件中的spring下添加如下配置内容。

1
2
3
4
5
6
7
8
9
10
# Redis相关配置
redis:
# 链接主机
host: localhost
# 连接密码
password:
# 端口号
port: 6379
# 存储库
database: 0

4.3 RedisConfig

注意开启@EnableCaching注解,否则缓存配置不生效

RedisConfig主要为Redis的配置类,通过Spring Boot中的@Configuration@Bean注解将配置注入到容器中使之生效,具体代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Redis配置类
*
* @author zchengb
* @since 2021-01-16
*/
@EnableCaching
@Configuration
public class RedisConfig {
/**
* 配置自定义序列化的RedisTemplate
*
* @param redisConnectionFactory Redis连接工厂
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(new JavaTimeModule());
jackson2JsonRedisSerializer.setObjectMapper(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// Value的JSON序列化配置
template.setValueSerializer(jackson2JsonRedisSerializer);
// Hash-Value的JSON序列化配置
template.setHashValueSerializer(jackson2JsonRedisSerializer);
// Key的String序列化配置
template.setKeySerializer(stringRedisSerializer);
// Hash-Key的String序列化配置
template.setHashKeySerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

创建上述RedisConfig.java用于配置Redis序列化,即生成一个RedisTemplate<String, Object>对象可供各种情况使用。如可在项目中存储各种JSON形式的实体类至Redis缓存中,并可从缓存中读取反序列化为实体对象。

4.4 RedisManager

RedisManager主要作为工具类提供快速操作Redis的方法,其由一个IGlobalCache.java接口与RedisManager.java工具类实现而成,具体代码如下所示。

  • IGlobalCache.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
/**
* 全局接口缓存
*
* @author zchengb
* @since 2021-01-17
*/
public interface IGlobalCache {
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return 是否设置成功
*/
boolean expire(String key, long time);

/**
* 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
long getExpire(String key);

/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
boolean hasKey(String key);

/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
void del(String... key);

/**
* 普通缓存获取
*
* @param key 键
* @return
*/
Object get(String key);

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
boolean set(String key, Object value);

/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
boolean set(String key, Object value, long time);

/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
long incr(String key, long delta);

/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
long decr(String key, long delta);

/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return
*/
Object hget(String key, String item);

/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
Map<Object, Object> hmget(String key);

/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
boolean hmset(String key, Map<String, Object> map);

/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
boolean hmset(String key, Map<String, Object> map, long time);

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
boolean hset(String key, String item, Object value);

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
boolean hset(String key, String item, Object value, long time);

/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
void hdel(String key, Object... item);

/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
boolean hHasKey(String key, String item);

/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
double hincr(String key, String item, double by);

/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
double hdecr(String key, String item, double by);

/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
Set<Object> sGet(String key);

/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
boolean sHasKey(String key, Object value);

/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
long sSet(String key, Object... values);

/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
long sSetAndTime(String key, long time, Object... values);


/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
long sGetSetSize(String key);

/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
long setRemove(String key, Object... values);

/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
List<Object> lGet(String key, long start, long end);

/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
long lGetListSize(String key);

/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
Object lGetIndex(String key, long index);

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return 是否设置成功
*/
boolean lSet(String key, Object value);

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 有效时间
* @return 是否设置成功
*/
boolean lSet(String key, Object value, long time);

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return 是否设置成功
*/
boolean lSetAll(String key, List<Object> value);


/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 是否设置成功
*/
boolean lSetAll(String key, List<Object> value, long time);

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return 是否设置成功
*/

boolean rSet(String key, Object value);

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 是否设置成功
*/

boolean rSet(String key, Object value, long time);

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return 是否设置成功
*/
boolean rSetAll(String key, List<Object> value);

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 是否设置成功
*/
boolean rSetAll(String key, List<Object> value, long time);

/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return 是否设置成功
*/
boolean lUpdateIndex(String key, long index, Object value);

/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
long lRemove(String key, long count, Object value);

/**
* 从redis集合中移除[start,end]之间的元素
*
* @param key 键
* @param start 起始下标
* @param end 结束下标
*/

void rangeRemove(String key, Long start, Long end);

/**
* 返回当前redisTemplate
*
* @return RedisTemplate
*/
RedisTemplate<String, Object> getRedisTemplate();
}
  • RedisManager.java

其中@Getter注解为使用了Lombok插件

此工具类不采用静态(static)形式主要是为了防止并发情况下造成的意外情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/**
* Redis管理器
*
* @author zchengb
* @since 2021-01-17
*/
@Getter
@Component
public class RedisCacheManager implements IGlobalCache {
@Resource
private RedisTemplate<String, Object> redisTemplate;

@Override
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public long getExpire(String key) {
Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
return expire == null ? 0L : expire;
}

@Override
public boolean hasKey(String key) {
try {
Boolean hasKey = redisTemplate.hasKey(key);
return hasKey != null ? hasKey : false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}

@Override
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

@Override
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
Long increment = redisTemplate.opsForValue().increment(key, delta);
return increment != null ? increment : 0L;
}

@Override
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
Long increment = redisTemplate.opsForValue().increment(key, -delta);
return increment != null ? increment : 0L;
}

@Override
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

@Override
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

@Override
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}

@Override
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}

@Override
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}

@Override
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}

@Override
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

@Override
public boolean sHasKey(String key, Object value) {
try {
Boolean member = redisTemplate.opsForSet().isMember(key, value);
return member != null && member;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public long sSet(String key, Object... values) {
try {
Long add = redisTemplate.opsForSet().add(key, values);
return add == null ? 0L : add;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

@Override
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count == null ? 0L : count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

@Override
public long sGetSetSize(String key) {
try {
Long size = redisTemplate.opsForSet().size(key);
return size != null ? size : 0L;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

@Override
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count == null ? 0L : count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

@Override
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

@Override
public long lGetListSize(String key) {
try {
Long size = redisTemplate.opsForList().size(key);
return size == null ? 0L : size;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

@Override
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

@Override
public boolean lSetAll(String key, List<Object> value) {
try {
redisTemplate.opsForList().leftPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().leftPushIfPresent(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().leftPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean lSetAll(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().leftPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean rSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean rSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}

@Override
public boolean rSetAll(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}

@Override
public boolean rSetAll(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove == null ? 0L : remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

@Override
public void rangeRemove(String key, Long stard, Long end) {
try {
redisTemplate.opsForList().trim(key, stard, end);
} catch (Exception e) {
e.printStackTrace();
}
}
}

至此,Redis与Spring Boot整合完成。

5. 主从复制

主从复制为了减缓服务器压力,架构中经常会使用,最低标准分为一主二从,即一台主机两台从机,其中主机可读可写,而从机只可读不可写入

1
2
3
4
5
6
7
8
9
10
11
12
# 查看当前库的信息
127.0.0.1:6379> info replication
# Replication
# 角色
role:master
# 从机数量
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

修改三台机器的redis.conf配置文件时,应注意修改端口、PID进程文件名称、log文件名称和dump.rdb名称

默认情况下,每台Redis服务器都是主节点,一般只需要配置从机即可。

5.1 命令行形式

可在从机下通过redis-cli命令行工具中的SLAVEOF IPADDRESS PORT命令配置主机,如SLAVEOF 127.0.0.1 6379,需要注意的是,通过命令行配置的主从模式是临时的,即重启后主从模式将会失效

5.2 配置文件形式

redis.conf配置文件中按照slaveof <masterip> <masterport>格式写入主机信息。需要注意的是,如果主机有认证密码则应以masterauth <master-password>格式继续写入主机连接密码。

随后分别启动主机与从机,分别在从机与主机查看当前主从信息,至此主从复制配置完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 从机
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:43
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

# 主机
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=71,lag=0
master_repl_offset:71
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:70

6. 哨兵模式(Redis Sentinel)

哨兵模式(Redis Sentinel)是Redis高可用的实现方案,Sentinel是一个管理多个Redis实例的工具,它可以对Redis进行监控、通知和故障转移

主从复制与哨兵模式

一般Redis采用主从复制模式,而其中的主机如果断开,则需要手动在从机群中指定一个新主机,这样费时费力,采用哨兵模式进行监控则可以由其自动监控Redis集群,当主机宕机时,自动监控并在内部选举出新主机进行通知。

6.1 下线判断

默认情况下,每个Sentinel节点都会进行发送PING命令来确认Redis节点与Sentinel节点是否在线。

a. 主观下线

主观下线适用于所有主节点与从节点。如果down-after-milliseconds毫秒内,Sentinel没有收到目标节点的有效回复,则会判定该节点为主观下线

b. 客观下线

客观下线只适用于主节点,如果主节点出现故障,Sentinel节点会通过sentinel is-master-down-by-addr命令,向其他Sentinel节点询问对该节点的状态判断,如果超过<quorum>个数的Sentinel节点认为该主节点不可达,则Sentinel节点会判断该主节点为客观下线

6.2 部署须知

  • 一个稳健的哨兵集群应该保证至少三台Sentinel实例,并保证这些实例放置在不同的机器上
  • Sentinel无法保证强一致性
  • 常见的客户端应用库都支持Sentinel
  • Sentinel需要通过不断的测试与观察,才能保证高可用。

6.3 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 哨兵sentinel实例运行的端口,默认26379  
port 26379
# 哨兵sentinel的工作目录
dir ./

# 哨兵sentinel监控的redis主节点的
## ip:主机ip地址
## port:哨兵端口号
## master-name:可以自己命名的主节点名字(只能由字母A-z、数字0-9 、这三个字符".-_"组成。)
## quorum:当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass <foobared>,所有连接Redis实例的客户端都要提供密码。
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456

# 指定主节点应答哨兵sentinel的最大时间间隔,超过这个时间,哨兵主观上认为主节点下线,默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

# 指定了在发生failover主备切换时,最多可以有多少个slave同时对新的master进行同步。这个数字越小,完成failover所需的时间就越长;反之,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为1,来保证每次只有一个slave,处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间failover-timeout,默认三分钟,可以用在以下这些方面:
## 1. 同一个sentinel对同一个master两次failover之间的间隔时间。
## 2. 当一个slave从一个错误的master那里同步数据时开始,直到slave被纠正为从正确的master那里同步数据时结束。
## 3. 当想要取消一个正在进行的failover时所需要的时间。
## 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来同步数据了
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

# 当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本。一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
# 对于脚本的运行结果有以下规则:
## 1. 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10。
## 2. 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
## 3. 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

根据上述配置文件配置好后使用sudo redis-sentinel /usr/local/redis-sentinel/sentinel-xxx.conf分别启动Sentinel节点。

6.4 常用命令

  • 检查其他 Sentinel 节点的状态,返回 PONG 为正常。
1
$ PING sentinel
  • 显示被监控的所有 主节点 以及它们的状态。
1
$ SENTINEL masters
  • 显示指定 主节点 的信息和状态。
1
$ SENTINEL master <master_name>
  • 显示指定 主节点 的所有 从节点 以及它们的状态。
1
$ SENTINEL slaves <master_name>

返回指定 主节点IP 地址和 端口。如果正在进行 failover 或者 failover 已经完成,将会显示被提升为 主节点从节点IP 地址和 端口

1
$ SENTINEL get-master-addr-by-name <master_name>
  • 重置名字匹配该 正则表达式 的所有的 主节点 的状态信息,清除它之前的 状态信息,以及 从节点 的信息。
1
$ SENTINEL reset <pattern>
  • 强制当前 Sentinel 节点执行 failover,并且不需要得到其他 Sentinel 节点的同意。但是 failover 后会将 最新的配置 发送给其他 Sentinel 节点。
1
$ SENTINEL failover <master_name>